import { Matrix3 } from '../math/Matrix3'
import { Vector2 as v2 } from '../math/Vector2'
import { Camera } from './Camera'

class Vertex {
  horizontal: number[]
  vertical: number[]
  matrixAutoUpdate: boolean
  position: v2
  rotate: number
  scale: v2
  transformOrigin: v2

  /* 本地模型矩阵 */
  get matrix(): Matrix3 {
    const { position, rotate, scale } = this;
    return new Matrix3()
      .scale(scale.x, scale.y)
      .rotate(rotate)
      .translate(position.x, position.y);
  }
  // 原点
  get origin() {
    if (!this.horizontal.length || !this.vertical.length)
      return new v2()
    const x = this.horizontal[0]
    const y = this.vertical[1]
    return new v2(x, y)
  }
  // 结束点（原点对角点 ---> 矩形右下角点位）
  get end() {
    if (!this.horizontal.length || !this.vertical.length)
      return new v2()
    const x = this.horizontal[this.horizontal.length - 2]
    const y = this.vertical[this.vertical.length - 1]
    return new v2(x, y)
  }
  // 矩阵宽高
  get size() {
    if (!this.horizontal.length || !this.vertical.length)
      return new v2()
    const x1 = this.horizontal[0]
    const y1 = this.vertical[1]
    const x2 = this.horizontal[this.horizontal.length - 2]
    const y2 = this.vertical[this.vertical.length - 1]
    const w = x2 - x1
    const h = y2 - y1
    return new v2(w, h)
  }
  // 横坐标起点
  get horizontalOrigin() {
    if (!this.horizontal.length)
      return new v2()
    const x = this.horizontal[0]
    const y = this.horizontal[1]
    return new v2(x, y)
  }
  // 纵坐标起点
  get verticalOrigin() {
    if (!this.vertical.length)
      return new v2()
    const x = this.vertical[0]
    const y = this.vertical[1]
    return new v2(x, y)
  }
  // 横坐标终点
  get horizontalEnd() {
    if (!this.horizontal.length)
      return new v2()
    const x = this.horizontal[this.horizontal.length - 2]
    const y = this.horizontal[this.horizontal.length - 1]
    return new v2(x, y)
  }
  // 纵坐标终点
  get verticalEnd() {
    if (!this.vertical.length)
      return new v2()
    const x = this.vertical[this.vertical.length - 2]
    const y = this.vertical[this.vertical.length - 1]
    return new v2(x, y)
  }
  // 坐标轴原点坐标
  get axesOrigin() {
    if (!this.horizontal.length || !this.vertical.length)
      return new v2()
    const x = this.vertical[0]
    const y = this.horizontal[1]

    return new v2(x, y)
  }
  constructor() {
    this.horizontal = []
    this.vertical = []

    this.rotate = 0
    this.scale = new v2(1, 1)
    this.position = new v2()
    this.transformOrigin = new v2()

  }
  // 根据索引获取对应点位
  getPointAt(type: 'x' | 'y', index: number) {
    const list = type === 'x' ? this.horizontal : this.vertical
    if (!list.length) return new v2()

    const i2 = index * 2
    return new v2(list[i2], list[i2 + 1])
  }
  // 应用矩阵
  applyMatrix3(matrix: Matrix3) {
    if (this.matrixAutoUpdate) this.updateMatrix()
    this.matrix.premultiply(matrix)
  }
  // 更新矩阵
  updateMatrix() {
    const { position, rotate, scale } = this
    this.matrix.scale(scale.x, scale.y).rotate(rotate).translate(position.x, position.y)
  }
  // 重置矩阵
  resetMatrix() {
    this.matrix.identity()
    // this.decomposeModelMatrix(this.matrix)
    this.rotate = 0
    this.scale = new v2(1, 1)
    this.position = new v2()
  }
  // 更新顶点数据
  updateVertives() {
    this.updateMatrix()
    const { horizontal, vertical } = this

    for (let i = 0; i < horizontal.length / 2; i++) {
      const { x, y } = this.getPointAt('x', i).applyMatrix3(this.matrix)
      const i2 = i * 2
      horizontal[i2] = x
      horizontal[i2 + 1] = y
    }
    for (let i = 0; i < vertical.length / 2; i++) {
      const { x, y } = this.getPointAt('y', i).applyMatrix3(this.matrix)
      const i2 = i * 2
      vertical[i2] = x
      vertical[i2 + 1] = y
    }
  }
  /* 将矩阵分解到当期对象的position, rotate, scale中 */
  decomposeModelMatrix(m: Matrix3) {
    const e = [...m.elements];
    // 位移量
    this.position.set(e[6], e[7]);
    // 缩放量
    let sx = new v2(e[0], e[1]).length();
    const sy = new v2(e[3], e[4]).length();
    const det = m.determinant();
    if (det < 0) {
      sx = -sx;
    }
    this.scale.set(sx, sy);
    // 旋转量
    let ang = Math.atan2(e[1] / sx, e[0] / sx);
    if (ang < 0) {
      ang += Math.PI * 2;
    }
    this.rotate = ang;
  }
  /* 先变换(缩放+旋转)后位移 */
  transform(ctx: CanvasRenderingContext2D) {
    const { position, rotate, scale, transformOrigin } = this;
    ctx.translate(position.x, position.y);
    ctx.translate(transformOrigin.x, transformOrigin.y)
    ctx.rotate(rotate);
    ctx.scale(scale.x, scale.y);
    ctx.translate(-transformOrigin.x, -transformOrigin.y)
  }
}

export default class {
  canvas: HTMLCanvasElement
  ctx: CanvasRenderingContext2D
  width: number
  height: number
  size: v2
  scale: number
  offset: v2
  space: number
  padding: number
  vertex: Vertex
  camera: Camera
  zoomCount: number
  showAxes: boolean

  constructor() {
    this.width = 1200
    this.height = 800

    const dpr = window.devicePixelRatio
    const width = Math.round(this.width * dpr)
    const height = Math.round(this.height * dpr)

    this.canvas = document.createElement('canvas')
    this.canvas.width = width
    this.canvas.height = height
    this.canvas.style.width = width + 'px'
    this.canvas.style.height = height + 'px'
    this.canvas.style.border = '1px solid #ccc'
    this.ctx = this.canvas.getContext('2d')

    document.body.appendChild(this.canvas)

    this.size = new v2(this.width, this.height)
    this.offset = new v2()
    this.space = 20
    this.padding = this.space * 5
    this.vertex = new Vertex()
    this.camera = new Camera()
    // @ts-ignore
    window.camera = this.camera
    this.zoomCount = 100
    this.showAxes = true
  }
  init() {
    this.createPoints()

    this.render()
    this.handleEvents()
    // window.requestAnimationFrame(this.animte.bind(this))

    // const { ctx } = this
    // const points = [
    //   new v2(100, 100),
    //   new v2(200, 200),
    //   new v2(300, 300),
    // ]
    // const origin = new v2(300, 300)
    // const scale = new v2(1.5, 1.5)
    // const matrix = new Matrix3()

    // matrix.translate(-origin.x, -origin.y);
    // matrix.scale(scale.x, scale.y)
    // matrix.translate(origin.x, origin.y);

    // points.forEach(v => {
    //   v.applyMatrix3(matrix)
    // })

    // points.forEach(v => {
    //   ctx.save()
    //   ctx.beginPath()
    //   ctx.arc(v.x, v.y, 10, 0, Math.PI * 2)
    //   ctx.fill()
    //   ctx.restore()
    // })
  }
  destory() {
    this.canvas?.remove()
  }
  createPoints() {
    const { size, padding } = this
    const { x: w, y: h } = size
    const distance = padding
    const lengthX = Math.ceil(w / distance)
    const lengthY = Math.ceil(h / distance)

    for (let i = 0; i < lengthX + 1; i++) {
      this.vertex.horizontal.push(i * distance, h / 2)
    }
    for (let i = 0; i < lengthY + 1; i++) {
      this.vertex.vertical.push(w / 2, i * distance)
    }
  }
  drawLine(ctx: CanvasRenderingContext2D, [x1, y1, x2, y2], style: string) {
    ctx.beginPath()
    ctx.moveTo(x1, y1)
    ctx.lineTo(x2, y2)
    ctx.strokeStyle = style
    ctx.lineWidth = 1
    ctx.closePath()
    ctx.stroke()
  }
  drawText(ctx: CanvasRenderingContext2D, [x, y], style = 'balck', text: string = '') {
    ctx.textAlign = 'right'
    ctx.textBaseline = 'top'
    ctx.fillText(text || (x + '.' + y), x, y)
  }
  drawGrid(ctx: CanvasRenderingContext2D) {
    const { vertex, space, showAxes, zoomCount } = this
    const {
      horizontal,
      vertical,
      origin,
      size,
      horizontalOrigin,
      horizontalEnd,
      verticalOrigin,
      verticalEnd,
      axesOrigin
    } = vertex
    const { x: w, y: h } = size

    ctx.save()
    this.vertex.transform(ctx)

    for (let i = 0; i < horizontal.length / 2; i++) {
      const point = vertex.getPointAt('x', i)

      if (showAxes) {
        this.drawLine(ctx, [point.x, origin.y, point.x, h], '#ccc')
        for (let j = 1; j < 5; j++) {
          this.drawLine(ctx, [point.x + space * j, origin.y, point.x + space * j, h], '#eee')
        }
      }

      const scale = (point.x - axesOrigin.x) / zoomCount
      this.drawText(ctx, [point.x, point.y], '#ccc', String(scale))

    }
    for (let i = 0; i < vertical.length / 2; i++) {
      const point = vertex.getPointAt('y', i)

      if (showAxes) {
        this.drawLine(ctx, [origin.x, point.y, w, point.y], '#ccc')
        for (let j = 1; j < 5; j++) {
          this.drawLine(ctx, [origin.x, point.y + space * j, w, point.y + space * j], '#eee')
        }
      }

      const scale = (axesOrigin.y - point.y) / zoomCount
      this.drawText(ctx, [point.x, point.y], '#ccc', String(scale))
    }

    // 绘制主轴
    // X
    this.drawLine(ctx, [horizontalOrigin.x, horizontalOrigin.y, horizontalEnd.x, horizontalEnd.y], 'black')
    // Y
    this.drawLine(ctx, [verticalOrigin.x, verticalOrigin.y, verticalEnd.x, verticalEnd.y], 'black')

    ctx.restore()
  }
  handleEvents() {
    let enable = false
    const origin = new v2(), offset = new v2()
    const { vertex, canvas, padding, width, height } = this

    const down = (event: MouseEvent) => {
      enable = true

      origin.set(event.pageX, event.pageY)
      offset.set(0, 0)
    }
    const move = (event: MouseEvent) => {
      if (!enable) return

      offset.copy(new v2(event.pageX, event.pageY).sub(origin))
      vertex.position.copy(offset)
      const begin = vertex.origin.applyMatrix3(vertex.matrix)
      const end = vertex.end.applyMatrix3(vertex.matrix)
      if (begin.x >= 0 || end.x <= width) {
        const p1 = vertex.horizontalOrigin
        const p2 = vertex.horizontalEnd
        vertex.horizontal.unshift(p1.x - padding, p1.y)
        vertex.horizontal.push(p2.x + padding, p2.y)
      }
      if (begin.y >= 0 || end.y <= height) {
        const p1 = vertex.verticalOrigin
        const p2 = vertex.verticalEnd
        vertex.vertical.unshift(p1.x, p1.y - padding)
        vertex.vertical.push(p2.x, p2.y + padding)
      }

      this.render()
    }
    const up = () => {
      enable = false
      vertex.updateVertives()
      vertex.resetMatrix()
    }
    let scale = 1, speed = this.space
    const handleWheel = (event: WheelEvent) => {
      const count = event.deltaY > 0 ? -1 : 1

      this.zoomCount += count * 2
      if (this.zoomCount <= 1) this.zoomCount = 1
      if (this.zoomCount >= 200) this.zoomCount = 200

      scale += count * 0.01

      if (scale < 1) scale = 1.6
      else if (scale > 1.6) scale = 1
      vertex.scale.set(scale)
      const { axesOrigin, origin, end } = vertex
      const matrix = new Matrix3().scale(scale, scale)

      const point = this.getoffset(event.pageX, event.pageY)
      const center = new v2(width / 2, height / 2)
      const offset = axesOrigin.clone().sub(point).multiplyScalar(0.5)
      offset.add(origin)

      const p1 = axesOrigin.clone().applyMatrix3(matrix).sub(axesOrigin).multiplyScalar(-1)
      vertex.position.copy(p1)

      // const { axesOrigin, horizontal, vertical } = vertex

      // const matrix = new Matrix3()
      //   .translate(-axesOrigin.x, -axesOrigin.y)
      //   .scale(scale, scale)
      //   .translate(axesOrigin.x, axesOrigin.y)

      // const pointx = [], pointy = []

      // for (let i = 0; i < horizontal.length; i++) {
      //   const point = vertex.getPointAt('x', i)
      //   point.applyMatrix3(matrix)
      //   pointx.push(point.x, point.y)
      // }
      // for (let i = 0; i < vertical.length; i++) {
      //   const point = vertex.getPointAt('x', i)
      //   point.applyMatrix3(matrix)
      //   pointy.push(point.x, point.y)
      // }
      // vertex.scale.set(scale)
      // console.log(pointx);

      // vertex.horizontal = pointx
      // vertex.vertical = pointy

      // {

      //   const { horizontal, vertical, origin, axesOrigin } = vertex

      //   const point = this.getoffset(event.pageX, event.pageY)

      //   speed += 2 * count
      //   if (speed < 20) speed = 30
      //   else if (speed > 30) speed = 20
      //   this.space = speed

      //   const offset = axesOrigin.clone().sub(point).multiplyScalar(0.5)

      //   const pointx = [], pointy = []
      //   {
      //     let pre = vertex.getPointAt('x', 0)
      //     for (let i = 0; i < horizontal.length / 2; i++) {
      //       if (i != 0)
      //         pre.x += speed * 5
      //       else {
      //         pre.y += (vertex.vertical.length / 2 - 1) * 5 * count
      //       }
      //       pointx.push(pre.x, pre.y)
      //     }
      //   }

      //   {
      //     let pre = vertex.getPointAt('y', 0)
      //     for (let i = 0; i < vertical.length / 2; i++) {
      //       if (i != 0)
      //         pre.y += speed * 5
      //       else {
      //         pre.x += (vertex.horizontal.length / 2 - 1) * 5 * count
      //       }
      //       pointy.push(pre.x, pre.y)
      //     }
      //   }
      //   vertex.horizontal = pointx
      //   vertex.vertical = pointy

      // }

      this.render()
    }
    canvas.addEventListener('mousedown', down)
    canvas.addEventListener('mousemove', move)
    canvas.addEventListener('mouseup', up)
    canvas.addEventListener('wheel', handleWheel)
  }
  doScale(deltaY: number, origin?: v2) {
    const { camera } = this

    const step = 0.02

    if (deltaY > 0) {
      // camera.zoom /= scale
      camera.zoom += step
    } else {
      // camera.zoom *= scale
      camera.zoom -= step
    }
    // if (camera.zoom >= 1.2 || camera.zoom <= 0.8) {
    //   camera.zoom = 1
    //   camera.position.setScalar(0)
    //   return
    // }


    // if (origin) {
    //   const P1 = new v2().addVectors(origin, camera.position)
    //   const P2 = P1.clone().multiplyScalar(1 / scale)
    //   const p = P2.sub(P1)

    //   deltaY < 0 ? camera.position.add(p) : camera.position.sub(p)
    // }

  }
  render() {
    const { width, height, ctx, camera } = this

    this.ctx.clearRect(0, 0, width, height)

    ctx.save()

    // camera.transformInvert(ctx)
    this.drawGrid(ctx)
    this.drawFn(ctx)

    ctx.restore()
  }
  animte() {
    this.render()

    window.requestAnimationFrame(this.animte.bind(this))
  }
  getoffset(pageX: number, pageY: number) {
    const rect = this.canvas.getBoundingClientRect()

    return new v2(pageX - rect.x, pageY - rect.y)
  }
  drawFn(ctx: CanvasRenderingContext2D) {
    const { vertex, width, height, zoomCount } = this
    const { size, axesOrigin, origin } = vertex
    axesOrigin.applyMatrix3(vertex.matrix)
    origin.applyMatrix3(vertex.matrix)

    const colors = [
      'blue',
      'pink',
      'yellow',
      'red',
      '#f0f'
    ]

    const fnList = [
      (x: number) => Math.sin(x),
      (x: number) => x,
      (x: number) => Math.cos(x),
      // (x: number) => Math.tan(x),
      (x: number) => 1 / x,
    ]

    fnList.forEach((fn, i) => {
      ctx.save()
      ctx.beginPath()
      ctx.strokeStyle = colors[i]
      ctx.lineWidth = 3

      for (let i = 0; i < size.x; i++) {
        const x = (origin.x + i - axesOrigin.x) / zoomCount

        let y = fn(x)
        if (isNaN(y)) continue

        y = axesOrigin.y - y * zoomCount

        if (x === 0 || x > width || y < 0 || y > height) {
          ctx.moveTo(x * zoomCount + axesOrigin.x, y)
        } else {
          ctx.lineTo((x * zoomCount + axesOrigin.x), y)
        }
      }

      ctx.stroke()
      ctx.restore()
    })

  }
}